package online.view.tag;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import javax.servlet.jsp.JspException;

import online.filter.FilterUtil;
import online.view.ViewUtil;

/**
 * URLパラメタ置換タグライブラリ
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class ReplaceParameterTag extends BaseTag {
	/** serialVersionUID */
	private static final long serialVersionUID = 2390070614428490390L;

	/** 変数用変数 */
	private String var = null;
	/** パラメタ文字列 */
	private String str = null;
	/** 項目名 */
	private String name = null;
	/** 置換値 */
	private String value = null;
	/** 項目を追加有効 */
	private boolean empty = false;

	/**
	 * リリース処理
	 */
	@Override
	public void release() {
		this.var = null;
		this.str = null;
		this.name = null;
		this.value = null;
		this.empty = false;
	}

	/**
	 * 変数設定
	 *
	 * @param val 保存変数名文字列
	 */
	public void setVar(final String val) {
		this.var = val;
	}

	/**
	 * パラメタ文字列設定
	 *
	 * @param val パラメタ文字列
	 */
	public void setStr(final String val) {
		this.str = val;
	}

	/**
	 * 項目名設定
	 *
	 * @param val 項目名
	 */
	public void setName(final String val) {
		this.name = val;
	}

	/**
	 * 置換値設定
	 *
	 * @param val 置換値
	 */
	public void setValue(final String val) {
		this.value = val;
	}

	/**
	 * 項目追加削除設定
	 *
	 * @param val 値が空でも項目が有効の場合 true を渡す。
	 */
	public void setEmpty(final String val) {
		this.empty = Boolean.parseBoolean(val);
	}

	/**
	 * @see javax.servlet.jsp.tagext.TagSupport#doStartTag()
	 */
	@Override
	public int doStartTag() throws JspException {
		try {
			// アトリビュートクリア
			if (this.var != null) {
				this.pageContext.removeAttribute(this.var);
			}

			if (this.name != null && this.value != null) {
				super.output(this.var,
						getQueryParam(toParameter(this.str), this.name, this.value, this.empty));
			} else {
				super.output(this.var, toParameter(this.str));
			}

			return SKIP_BODY;
		} finally {
			release();
		}
	}

	/**
	 * パラメタ文字列化
	 * @param val 指定パラメタ文字列
	 * @return パラメタ文字列
	 */
	private String toParameter(final String val) {
		String ret = val;
		if (ret == null) {
			ret = Objects.toString(FilterUtil.getQueryString(super.getRequest()), "");
		}
		return ret.replaceAll("[<>\"\\(\\)]", "");
	}

	/**
	 * クエリパラメタ取得
	 *
	 * @param s 元文字列
	 * @param nm 置換項目名
	 * @param val 置換値
	 * @param flg 項目追加削除フラグ
	 * @return クエリパラメタ
	 */
	private String getQueryParam(
			final String s, final String nm, final String val, final boolean flg) {
		List<String> names = new ArrayList<>(Arrays.asList(nm.split(",", -1)));
		List<String> vals = new ArrayList<>(Arrays.asList(val.split(",", -1)));
		List<String> strs = new ArrayList<>(Arrays.asList(s.split("&")));

		// 置換
		for (int i = 0; i < names.size(); i++) {
			vals.set(i, ViewUtil.encode(ViewUtil.taint(vals.get(i), null),
				super.getRequest().getCharacterEncoding()));
			if (replace(strs, names.get(i), vals.get(i), flg)) {
				names.remove(i);
				vals.remove(i);
				i--;
			}
		}

		// 追加
		for (int i = 0; i < names.size(); i++) {
			if (flg || !vals.get(i).isEmpty()) {
				strs.add(names.get(i) + "=" + vals.get(i));
			}
		}

		return concat(strs);
	}

	/**
	 * 置換処理
	 *
	 * @param strs 置換対象文字列リスト
	 * @param nm 置換項目名
	 * @param vl 置換値
	 * @param flg 項目追加削除フラグ
	 * @return 置換された場合 true を返す。
	 */
	private boolean replace(final List<String> strs,
			final String nm, final String vl, final boolean flg) {
		boolean ret = false;
		String item = nm + "=";
		for (int i = 0; i < strs.size(); i++) {
			if (strs.get(i).startsWith(item)) {
				if (vl.isEmpty() && !flg) {
					strs.remove(i);
					i--;
				} else {
					strs.set(i, nm + "=" + vl);
				}
				ret = true;
			}
		}
		return ret;
	}

	/**
	 * 文字列連結
	 *
	 * @param strs 連結文字列リスト
	 * @return 連結文字列
	 */
	private String concat(final List<String> strs) {
		StringBuilder sb = new StringBuilder();
		for (final String s : strs) {
			if (0 < sb.length()) {
				sb.append("&amp;");
			}
			sb.append(s);
		}
		return sb.toString();
	}
}
